﻿using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using SS.Common.DBBuilder.DBDataStructure;
using SS.Common.Logging;
using SS.Net.AppGen.Models;
using SS.Net.AppGen.Utils;
using System.IO;
using SS.Net.AppGen.Generators;
using System.Xml;
using System.Xml.Serialization;

namespace SS.Net.AppGen.WebGenerators.WebViewGenerator
{
    /// <summary>
    /// The generator is responsible for generating base view class
    /// </summary>
    [Export(typeof(BaseViewGenerator))]
    public sealed class BaseViewGenerator : BaseGenerator, IWebComponentsGenerator
    {
        #region Fields

        private static class Consts
        {
            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstScriptSubmitForm = "[ScriptSubmitForm]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstReferToListView = "[ReferToListView]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstReferToInsertView = "[ReferToInsertView]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstReferToDeleteRangeView = "[ReferToDeleteRangeView]";

            public const string ConstScriptEditInputParam = "[ScriptEditInputParam]";
            public const string ConstScriptEditData = "[ScriptEditData]";
            public const string ConstScriptCancelEditingInputParam = "[ScriptCancelEditingInputParam]";
            public const string ConstScriptCancelEditingData = "[ScriptCancelEditingData]";
            public const string ConstScriptDeleteInputParam = "[ScriptDeleteInputParam]";
            public const string ConstScriptDeleteData = "[ScriptDeleteData]";
        }

        /// <summary>
        /// The instance of _viewComponents, this object will be updated when deserialization pattern file
        /// </summary>
        private ViewComponents _viewComponents;

        /// <summary>
        /// The logger instance
        /// </summary>
        private readonly ILogger Logger = LoggerManager.GetLogger(typeof (BaseViewGenerator).Name);

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public BaseViewGenerator()
        {
            this.XMLTemplate = "BaseViewTemplate.xml";
            this.DeserializeTemplate();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Override this method to generate components
        /// </summary>
        /// <param name="db">Data source for generating</param>
        public void Generate(DataBase db)
        {
            foreach (Table tbl in db.Tables)
            {
                if (!this.IsItemExistent(BaseConsts.SkipTables, tbl.Name))
                {
                    this.GenerateContents(tbl);
                }
            }
        }

        /// <summary>
        /// Method for generating contents of controller components and save generated contents to a file
        /// </summary>
        /// <param name="tbl">Data source for generating</param>
        private void GenerateContents(Table tbl)
        {
            this.Logger.InfoFormat("Generate BaseView[{0}] --> Start", tbl.Name);

            try
            {
                var contents = new StringBuilder(BaseConsts.MAX_BYTES);
                var htmlContents = this._viewComponents.HtmlContent.Replace(BaseConsts.ConstTable, tbl.Name)
                    .Replace(BaseConsts.ConstGlobalNameSpace, WebConfiguration.Instance.NameSpace)
                    .Replace(Consts.ConstReferToListView, this.BuildReferToList(tbl))
                    .Replace(Consts.ConstReferToInsertView, this.BuildReferToInsert(tbl))
                    .Replace(Consts.ConstReferToDeleteRangeView, this.BuildReferToDeleteRange(tbl))
                    .Replace(BaseConsts.ConstPrimaryKey, tbl.GetPrimaryKey().Name)
                    .Replace(Consts.ConstScriptEditInputParam, this.BuildScriptEditInputParam(tbl))
                    .Replace(Consts.ConstScriptEditData, this.BuildScriptEditData(tbl))
                    .Replace(Consts.ConstScriptCancelEditingInputParam, this.BuildScriptCancelInputParam(tbl))
                    .Replace(Consts.ConstScriptCancelEditingData, this.BuildScriptCancelEditingData(tbl))
                    .Replace(Consts.ConstScriptDeleteInputParam, this.BuildScriptDeleteInputParam(tbl))
                    .Replace(Consts.ConstScriptDeleteData, this.BuildScriptDeleteData(tbl))
                    .Replace(Consts.ConstScriptSubmitForm, this.BuildScriptSubmitForm(tbl));

                contents.AppendLine(htmlContents);

                // Init path
                this.DirPath = Path.Combine(this.GetAppPath(), "WebGenerator", "Views", tbl.Name + "Manager");
                this.InitializeDir(this.DirPath);
                // Save contents to a file
                this.SaveFile(contents.ToString().TrimStart(), "Index.cshtml");
            }
            catch (Exception ex)
            {
                this.Logger.ErrorFormat("GenerateContents Error: {0}", ex.ToString());
                throw new Exception(ex.ToString());
            }

            this.Logger.InfoFormat("Generate BaseView[{0}] <-- End", tbl.Name);
        }

        private string BuildScriptDeleteInputParam(Table tbl)
        {
            var pk = tbl.GetPrimaryKey().Name;
            var result = string.Format("{0}", pk);
            return result;
        }

        private string BuildScriptDeleteData(Table tbl)
        {
            var pk = tbl.GetPrimaryKey().Name;
            var result = string.Format("{0}: {1}", pk, pk);
            return result;
        }

        private string BuildScriptEditInputParam(Table tbl)
        {
            string mainFK;
            var pk = tbl.GetPrimaryKey().Name;

            var result = tbl.IsTableWithMainForeignKey(out mainFK)
                                ? string.Format("{0}, {1}", pk, mainFK)
                                : string.Format("{0}", pk);

            return result;
        }

        private string BuildScriptEditData(Table tbl)
        {
            string mainFK;
            var pk = tbl.GetPrimaryKey().Name;

            var result = tbl.IsTableWithMainForeignKey(out mainFK)
                                ? string.Format("{0}: {1}, {2}: {3}", pk, pk, mainFK, mainFK)
                                : string.Format("{0}: {1}", pk, pk);

            return result;
        }

        private string BuildScriptCancelInputParam(Table tbl)
        {
            string mainFK;
            var pk = tbl.GetPrimaryKey().Name;

            var result = tbl.IsTableWithMainForeignKey(out mainFK)
                                ? string.Format("{0}, {1}", pk, mainFK)
                                : string.Format("{0}", pk);

            return result;
        }

        private string BuildScriptCancelEditingData(Table tbl)
        {
            string mainFK;
            var pk = tbl.GetPrimaryKey().Name;

            var result = tbl.IsTableWithMainForeignKey(out mainFK)
                                ? string.Format("{0}: {1}, {2}: {3}", pk, pk, mainFK, mainFK)
                                : string.Format("{0}: {1}", pk, pk);

            return result;
        }

        /// <summary>
        /// Build refer tag to list view
        /// </summary>
        /// <param name="tbl"></param>
        /// <returns></returns>
        private string BuildReferToList(Table tbl)
        {
            string mainFK;
            var result = string.Empty;

            if (tbl.IsTableWithMainForeignKey(out mainFK))
            {
                var fk = tbl.Attributes.FirstOrDefault(n => n.Name.Equals(mainFK, StringComparison.OrdinalIgnoreCase));
                var fkTbl = tbl.DataBase.Tables.FirstOrDefault(
                    n => fk != null && n.Name.Equals(fk.ReferTo, StringComparison.OrdinalIgnoreCase));
                if (fk != null)
                {
                    result = string.Format(
                        "@Url.Action(\"Select{0}By{1}\", \"{5}Manager\", new {{ {2} = Model.{3}[0].{4} }})",
                        tbl.Name, fk.ReferTo, mainFK, fk.ReferTo.Pluralize(), fkTbl.GetPrimaryKey().Name, tbl.Name);
                }
            }
            else
            {
                result = string.Format("@Url.Action(\"Select{0}\", \"{1}Manager\")", tbl.Name, tbl.Name);
            }

            return result;
        }

        /// <summary>
        /// Build refer tag to insert view
        /// </summary>
        /// <param name="tbl"></param>
        /// <returns></returns>
        private string BuildReferToInsert(Table tbl)
        {
            string mainFK;
            var result = string.Empty;

            if (tbl.IsTableWithMainForeignKey(out mainFK))
            {
                var fk = tbl.Attributes.FirstOrDefault(n => n.Name.Equals(mainFK, StringComparison.OrdinalIgnoreCase));
                var fkTbl = tbl.DataBase.Tables.FirstOrDefault(
                    n => fk != null && n.Name.Equals(fk.ReferTo, StringComparison.OrdinalIgnoreCase));
                if (fk != null)
                {
                    result = string.Format(
                        "@Url.Action(\"EnterInsert{0}\", \"{4}Manager\", new {{ {1} = Model.{2}[0].{3} }})",
                        tbl.Name, mainFK, fk.ReferTo.Pluralize(), fkTbl.GetPrimaryKey().Name, tbl.Name);
                }
            }
            else
            {
                result = string.Format("@Url.Action(\"EnterInsert{0}\", \"{1}Manager\")", tbl.Name, tbl.Name);
            }

            return result;
        }

        /// <summary>
        /// Build refer tag to delete range view
        /// </summary>
        /// <param name="tbl"></param>
        /// <returns></returns>
        private string BuildReferToDeleteRange(Table tbl)
        {
            string mainFK;
            var result = string.Empty;

            if (tbl.IsTableWithMainForeignKey(out mainFK))
            {
                var fk = tbl.Attributes.FirstOrDefault(n => n.Name.Equals(mainFK, StringComparison.OrdinalIgnoreCase));
                var fkTbl =
                    tbl.DataBase.Tables.FirstOrDefault(
                        n => fk != null && n.Name.Equals(fk.ReferTo, StringComparison.OrdinalIgnoreCase));
                if (fk != null)
                {
                    result =
                        string.Format(
                            "@Url.Action(\"EnterDeleteRangeOf{0}\", \"{4}Manager\", new {{ {1} = Model.{2}[0].{3} }})",
                            tbl.Name, mainFK, fk.ReferTo.Pluralize(), fkTbl.GetPrimaryKey().Name, tbl.Name);
                }
            }
            else
            {
                result = string.Format("@Url.Action(\"EnterDeleteRangeOf{0}\", \"{1}Manager\")", tbl.Name, tbl.Name);
            }

            return result;
        }

        /// <summary>
        /// Build script for submitting form
        /// </summary>
        /// <param name="tbl"></param>
        /// <returns></returns>
        private string BuildScriptSubmitForm(Table tbl)
        {
            string imgAttr;
            string fckAttr;
            var result = string.Empty;

            if (!tbl.IsTableWithImage(out imgAttr) && !tbl.IsTableWithFCK(out fckAttr))
            {
                var contents = new StringBuilder(BaseConsts.MAX_BYTES);

                if (tbl.IsUserTable())
                {
                    contents.AppendLine(string.Format("var submitForm{0} = function () {{", tbl.Name));
                    contents.Append(BaseConsts.TAB2).AppendLine("$('#MySubmitForm').submit();");
                    contents.Append(BaseConsts.TAB2).AppendLine("return;");
                    contents.Append(BaseConsts.TAB1).AppendLine("};");
                }
                else
                {
                    contents.AppendLine(string.Format("var submitForm{0} = function () {{", tbl.Name));
                    contents.Append(BaseConsts.TAB2).AppendLine("var errTxt = \"\";");

                    var firstAttr = true;
                    foreach (SS.Common.DBBuilder.DBDataStructure.Attribute attr in tbl.Attributes)
                    {
                        if (!attr.IsIdentify)
                        {
                            if (firstAttr)
                            {
                                firstAttr = false;
                                contents.Append(BaseConsts.TAB2).AppendLine(
                                    string.Format("var elem = $(\"#{0}\");", attr.Name));
                            }
                            else
                            {
                                contents.Append(BaseConsts.TAB2).AppendLine(
                                    string.Format("elem = $(\"#{0}\");", attr.Name));
                            }

                            contents.Append(BaseConsts.TAB2).AppendLine("if (elem.val() == \"\") {");
                            contents.Append(BaseConsts.TAB3).AppendLine(
                                string.Format("errTxt = \"The element {0} is empty\";", attr.Name));
                            contents.Append(BaseConsts.TAB2).AppendLine("}");
                        }
                    }

                    contents.Append(BaseConsts.TAB2).AppendLine("if (errTxt == \"\") {");
                    contents.Append(BaseConsts.TAB3).AppendLine("$('#MySubmitForm').submit();");
                    contents.Append(BaseConsts.TAB3).AppendLine("return;");
                    contents.Append(BaseConsts.TAB2).AppendLine("}");
                    contents.Append(BaseConsts.TAB2).AppendLine("app.logger.error(errTxt);");
                    contents.Append(BaseConsts.TAB1).AppendLine("};");
                }
                result = contents.ToString();
            }

            return result;
        }

        /// <summary>
        /// Deserialize Base Controller pattern file
        /// </summary>
        private void DeserializeTemplate()
        {
            var patternFile = Path.Combine(this.GetAppPath(), "Templates", "WebTemplates", this.XMLTemplate);

            using (XmlTextReader xreader = new XmlTextReader(patternFile))
            {
                xreader.Namespaces = false;
                try
                {
                    var x = new XmlSerializer(typeof (ViewComponents));
                    this._viewComponents = x.Deserialize(xreader) as ViewComponents;
                }
                catch (Exception ex)
                {
                    throw new Exception(string.Format("Cannot deserialize BaseViewTemplate, BaseViewTemplate => {0}",
                                                      ex.Message));
                }
            }
        }

        #endregion
    }
}
